#include <os/debug.h>
#include <os/driver.h>
#include <os/schedule.h>
#include <os/pipe.h>
#include <os/initcall.h>
#include <lib/bitop.h>
#include <lib/string.h>
#include <lib/stdio.h>
#include <lib/type.h>
#include <arch/io.h>
#include <arch/interrupt.h>
#include <sys/ioctl.h>
#include <sys/fcntl.h>

#include <driver/ptty.h>

//#define DEBUG_PTTY

iostatus_t PttyOpen(device_object_t *device, io_request_t *ioreq)
{
    iostatus_t status = IO_FAILED;
    device_extension_t *extension = device->device_extension;
    pipe_t *pipe_in;
    pipe_t *pipe_out;

    device_object_t *devobj;
    device_extension_t *devext;

    if (extension->type == PTTY_MASTER)
    {
        if (!extension->other_object && !extension->pipe_in && !extension->pipe_out) // no slave
        {
            // create a pipe
            pipe_in = CreatePipe();
            if (!pipe_in)
            {
                KPrint("[ptty] create in pipe failed!\n");
                goto err_pipe_in;
            }
            pipe_out = CreatePipe();
            if (!pipe_out)
            {
                KPrint("[ptty] create out pipe failed!\n");
                goto err_pipe_out;
            }

            char devname[DEVICE_NAME_LEN+1];
            memset(devname, 0, DEVICE_NAME_LEN);
            sprintf(devname, "%s%d", DEVICE_NAME_SLAVE, extension->device_id);
            status = IoCreateDevice(device->driver, sizeof(device_extension_t), devname, DEVICE_TYPE_VIRTUAL_CHAR, &devobj);
            if (status != IO_SUCCESS)
            {
                KPrint("[ptty] create slave device failed!\n");
                goto err_create_dev;
            }
            // neither io mode
            devobj->flags = 0;
            devext = (device_extension_t *)devobj->device_extension;
            devext->type = PTTY_SLAVE; // slave terminal
            devext->device_id = extension->device_id;

            extension->pipe_in = pipe_in;
            extension->pipe_out = pipe_out;
            extension->other_object = devobj;

            // for slave device,change in and out
            devext->pipe_in = pipe_out;
            devext->pipe_out = pipe_in;
            devext->other_object = device;
            devext->locked = 1; // locked
            devext->flags = 0;
            devext->opened = 0;
            extension->pgrp = -1;
        }
    }
    else
    {
        // had been locked
        if (extension->locked)
        {
            goto err_no;
        }
    }
    extension->opened = 1; // opened
    extension->pgrp = cur_task->processgroup_id;

    status = IO_SUCCESS;

    KPrint("[ptty open] open device %s ok\n",device->name.text);
    KPrint("[ptty open] open other devcie %s ok\n",devobj->name.text);
    goto err_no;

err_create_dev:
    DestroyPipe(pipe_out);
err_pipe_out:
    DestroyPipe(pipe_in);
err_pipe_in:
err_no:
    ioreq->io_status.status = status;
    ioreq->io_status.info = 0;
    IoCompleteRequest(ioreq);
    return status;
}

iostatus_t PttyClose(device_object_t *device, io_request_t *ioreq)
{
    iostatus_t status = IO_FAILED;
    device_extension_t *extension = device->device_extension;
    device_extension_t *devext;

    //destroy slave device when close 
    if (extension->type == PTTY_MASTER)
    {
        extension->locked = 0;

        extension->opened = 0;
        if (extension->other_object)
        {
            devext = extension->other_object->device_extension;
            if (!devext->opened) // close
            {
                PipeClear(devext->pipe_in);
                PipeClear(devext->pipe_out);
            }
        }
    }
    else
    {
        if (extension->type == PTTY_SLAVE)
        {
            // device locked
            if (extension->locked)
                goto err_no_found;

            extension->locked = 1;
            extension->opened = 0;

            if (extension->other_object)
            {
                devext = extension->other_object->device_extension;
                if (!devext->opened) // closed
                {
                    PipeClear(devext->pipe_in);
                    PipeClear(devext->pipe_out);
                }
            }
        }
    }
    extension->flags = 0;
    extension->pgrp = -1;

    status = IO_SUCCESS;
err_no_found:
    ioreq->io_status.status = status;
    ioreq->io_status.info = 0;
    IoCompleteRequest(ioreq);
    return status;
}

iostatus_t PttyRead(device_object_t *device, io_request_t *ioreq)
{
    iostatus_t status=IO_FAILED;

    device_extension_t *extension = device->device_extension;
    uint8_t *buff = (uint8_t *)ioreq->user_buff;
    int len = ioreq->parame.read.len;

    #ifdef DEBUG_PTTY
    KPrint("[ptty read] device %s len %d\n",device->name.text,len);
    #endif 

    if ((len = PipeRead(extension->pipe_in->id, buff, len)) < 0)
        goto err_rd;

err_rd:
    #ifdef DEBUG_PTTY
    //KPrint("%s: read %d bytes!", __func__, len);
    #endif 

    status = IO_SUCCESS;
    ioreq->io_status.status = status;
    ioreq->io_status.info = len;
    IoCompleteRequest(ioreq);
    return status;
}

static int __PttyWrite(device_extension_t *extension, char *buff, int len)
{
    char *p = (char *)buff;
    int n = 0;

    while (*p)
    {
        switch (*p)
        {
        case '\003': // CTRL+INT
            if (extension->type == PTTY_MASTER)
            {
                // if is master,just send int exception to slave
                if (extension->other_object)
                {
                    device_extension_t *slave = extension->other_object->device_extension;
                    if (slave)
                    {
                        ExceptionSendGroup(slave->pgrp, EXC_CODE_INT);
                    }
                }
                return 0;
            }
            break;
        default:
            if (PipeWrite(extension->pipe_out->id, p, 1) < 0)
                return -1;
            n++;
            break;
        }

        p++;
    }

    return n;
}

iostatus_t PttyWrite(device_object_t *device, io_request_t *ioreq)
{
    device_extension_t *extension = device->device_extension;
    iostatus_t status = IO_SUCCESS;
    char *p = (char *)ioreq->user_buff;
    int len = ioreq->parame.write.len;

    #ifdef DEBUG_PTTY
    KPrint("[ptty write] device %s buff %s len %d\n",device->name.text,p,len);
    #endif

    if ((len = __PttyWrite(extension, p, len)) < 0)
        goto err_wr;

err_wr: 
    #ifdef DEBUG_PTTY
    KPrint("%s: write %d bytes!\n", __func__, len);
    #endif 

    status = IO_SUCCESS;
    ioreq->io_status.status = status;
    ioreq->io_status.info = len;
    IoCompleteRequest(ioreq);
    return status;
}

/// @brief 
/// @param device 
/// @param ioreq 
/// @return 
iostatus_t PttyDevCtl(device_object_t *device, io_request_t *ioreq)
{
    device_extension_t *extension = device->device_extension;
    iostatus_t status = IO_SUCCESS;
    int flags;
    uint64_t cmd = ioreq->parame.devctl.code;
    uint64_t arg = ioreq->parame.devctl.arg;

    switch (cmd)
    {
    case TTYIO_GETPTNUM:
        if (extension->other_object && extension->type == PTTY_MASTER)
        {
            extension = extension->other_object->device_extension;
            *(uint32_t *)arg = extension->device_id;
        }
        else
        {
            status = IO_FAILED;
        }
        break;
    case TTYIO_SETPTLOCK:
        if (extension->other_object && extension->type == PTTY_MASTER)
        {
            extension = extension->other_object->device_extension;
            extension->locked = *(uint8_t *)arg;
        }
        else
        {
            status = IO_FAILED;
        }
        break;
    case TTYIO_SETFLAGS:
        extension->flags = *(uint32_t *)arg;
        if (extension->flags & PTTY_RDNOBLK)
        {
            KPrint("[ptty] ctrl: set RDNOBLK\n");
            flags = O_NONBLOCK;
            if (PipeIoCtl(extension->pipe_in->id, F_SETFL, &flags, 0) < 0)
                status = IO_FAILED;
        }
        if (extension->flags & PTTY_WRNOBLK)
        {
            KPrint("[ptty] ctrl: set WRNOBLK\n");

            flags = O_NONBLOCK;
            if (PipeIoCtl(extension->pipe_out->id, F_SETFL, &flags, 1) < 0)
                status = IO_FAILED;
        }
        break;
    case TTYIO_GETFLAGS:
        *(uint32_t *)arg = extension->flags;
        break;
    case TTYIO_SETPGROUP:
        extension->pgrp=*(uint64_t*)arg;
        break;
    case TTYIO_GETPGROUP:
        *(uint64_t*)arg=extension->pgrp;
        break;
    case TTYIO_GETFRONTGROUP: //get front group task
        if(extension->other_object&&extension->type==PTTY_MASTER)
        {
            extension=extension->other_object->device_extension;
            if(extension->pgrp>0)
            {
                *(uint64_t*)arg=extension->pgrp;
            }
        }
        else 
        {
            status=IO_FAILED;
        }
        break;
    case TTYIO_ISTTY:
        *(uint8_t*)arg=1;
        break;
    case TTYIO_NAME:
        {
            char *buff=(char *)arg;
            strncpy(buff,device->name.text,strlen(device->name.text));
        }
        break;
    default:
        break;
    }

    ioreq->io_status.info=0;
    ioreq->io_status.status=status;
    IoCompleteRequest(ioreq);
    return status;
}

static iostatus_t PttyEnter(driver_object_t *driver)
{
    iostatus_t status=IO_FAILED;
    device_object_t *device;
    device_extension_t *extension;

    int i;
    char devname[DEVICE_NAME_LEN+1];

    for(i=0;i<PTM_NUM;i++)
    {
        memset(devname,0,DEVICE_NAME_LEN);
        sprintf(devname,"%s%d",DEVICE_NAME_MASTER,i);
        status=IoCreateDevice(driver,sizeof(device_extension_t),devname,DEVICE_TYPE_VIRTUAL_CHAR,&device);
        if(status!=IO_SUCCESS)
        {
            KPrint("[pttyy] %s: create device failed!\n",__func__);
            return status;
        }
        //neither io mode
        device->flags=0;
        extension=device->device_extension;
        extension->device_id=i;
        extension->type=PTTY_MASTER;
        extension->locked=0;
        extension->opened=0;
        extension->flags=0;
        extension->pgrp=-1;
        extension->pipe_in=NULL;
        extension->pipe_out=NULL;
        extension->other_object=NULL;
    }
    status=IO_SUCCESS;
    return status;
}

static iostatus_t PttyExit(driver_object_t *driver)
{
    device_object_t *device,*next;
    list_traversal_all_owner_to_next_safe(device,next,&driver->device_list,list)
    {
        IoDeleteDevice(device);
    }
    string_del(&driver->name);
    return IO_SUCCESS;
}

iostatus_t PttyDriverFunc(driver_object_t *driver)
{
    iostatus_t status = IO_SUCCESS;

    driver->driver_enter = PttyEnter;
    driver->driver_exit = PttyExit;

    driver->dispatch_fun[IOREQ_OPEN] = PttyOpen;
    driver->dispatch_fun[IOREQ_CLOSE] = PttyClose;
    driver->dispatch_fun[IOREQ_READ] = PttyRead;
    driver->dispatch_fun[IOREQ_WRITE] = PttyWrite;
    driver->dispatch_fun[IOREQ_DEVCTL] = PttyDevCtl;

    string_new(&driver->name, DRIVER_NAME, DRIVER_NAME_LEN);

    return status;
}

static __init void PttyDriverEntry()
{
    if (DriverObjectCreate(PttyDriverFunc) < 0)
    {
        KPrint("[driver] %s create driver failed!\n", __func__);
    }
}

driver_initcall(PttyDriverEntry);